package net.sf.nmedit.nomad.core.swing.explorer; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.SystemColor; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragGestureRecognizer; import java.awt.dnd.DragSource; import java.awt.dnd.DragSourceDragEvent; import java.awt.dnd.DragSourceDropEvent; import java.awt.dnd.DragSourceEvent; import java.awt.dnd.DragSourceListener; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.JComponent; import javax.swing.Timer; import javax.swing.plaf.TreeUI; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import net.sf.nmedit.nmutils.dnd.FileDnd; import net.sf.nmedit.nmutils.io.FileUtils; public class FileExplorerTreeUI extends ExplorerTreeUI { public void installUI(JComponent c) { super.installUI(c); DND dnd = new DND(); dnd.dragSource = DragSource.getDefaultDragSource(); DragGestureRecognizer dgr = dnd.dragSource.createDefaultDragGestureRecognizer(tree, DnDConstants.ACTION_COPY_OR_MOVE, dnd); dnd.dropTarget = new DropTarget(tree, DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK, dnd); } public static final DataFlavor FileSelectionFlavor = new DataFlavor(FileSelectionTransferable.class, "Nomad FileSelectionFlavor"); protected static DataFlavor uriFlavor = new DataFlavor("text/uri-list; charset=utf-8", "uri list"); protected static DataFlavor patchFlavor = new DataFlavor("text/patch", "patch text file"); protected class FileSelectionTransferable implements Transferable { TreePath[] getSelectedPaths() { return tree.getSelectionPaths(); } Transferable[] getSelectedTransferables() { TreePath paths[] = tree.getSelectionPaths(); ArrayList<Transferable> result = new ArrayList<Transferable>(); for (TreePath path : paths) { Object node = path.getLastPathComponent(); if (node instanceof Transferable) result.add((Transferable)node); } Transferable returnValue[] = new Transferable[result.size()]; return result.toArray(returnValue); } FileNode[] getSelectedFileNodes() { TreePath paths[] = tree.getSelectionPaths(); ArrayList<FileNode> result = new ArrayList<FileNode>(); for (TreePath path : paths) { Object node = path.getLastPathComponent(); if (node instanceof FileNode) result.add((FileNode)node); } FileNode returnValue[] = new FileNode[result.size()]; return result.toArray(returnValue); } public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (FileSelectionFlavor.match(flavor)) { return this; } else if (uriFlavor.match(flavor)) { ByteArrayOutputStream os = new ByteArrayOutputStream(); for (Transferable t : getSelectedTransferables()) { Object o = t.getTransferData(FileNode.fileFlavor); if (o instanceof File) { File f = (File)o; String path = f.getAbsoluteFile().toURI().toString() + "\n"; os.write(path.getBytes("utf-8")); } } return new ByteArrayInputStream(os.toByteArray()); } else { return null; } } public DataFlavor[] getTransferDataFlavors() { DataFlavor[] flavors = {FileSelectionFlavor, uriFlavor}; return flavors; } public boolean isDataFlavorSupported(DataFlavor flavor) { for (DataFlavor f: getTransferDataFlavors()) if (f.equals(flavor)) return true; return false; } } private class DND implements DragGestureListener, DragSourceListener, DropTargetListener { DragSource dragSource; DropTarget dropTarget; private Rectangle cueLine = new Rectangle(); private Color colorCueLine; private Timer timerHover; private TreePath pathLast = null; private TreePath pathLastExpanded = null; protected DND() { colorCueLine = new Color( SystemColor.controlShadow.getRed(), SystemColor.controlShadow.getGreen(), SystemColor.controlShadow.getBlue(), 64 ); timerHover = new Timer(1000, new ActionListener() { public void actionPerformed(ActionEvent e) { if (isRootPath(pathLast)) return; // Do nothing if we are hovering over the root node if (!tree.isExpanded(pathLast)) { tree.expandPath(pathLast); if (pathLastExpanded == null || !pathLast.isDescendant(pathLastExpanded)) { pathLastExpanded = pathLast; } } } }); timerHover.setRepeats(false); // Set timer to one-shot mode } public boolean isRootPath(TreePath path) { return isRootVisible() && tree.getRowForPath(path) == 0; } public void dragGestureRecognized(DragGestureEvent dge) { Point p = dge.getDragOrigin(); TreePath path = getClosestPathForLocation(tree, p.x, p.y); if (path == null) return; if (!tree.isPathSelected(path) && (tree.getSelectionPaths() == null || (tree.getSelectionPaths().length == 0))) { tree.setSelectionPath(path); } Transferable transferable = new FileSelectionTransferable(); dragSource.startDrag(dge, DragSource.DefaultMoveNoDrop, transferable, this); } public void dragDropEnd(DragSourceDropEvent dsde) { // no op } public void dragEnter(DragSourceDragEvent dsde) { // no op } public void dragExit(DragSourceEvent dse) { // no op } public void dragOver(DragSourceDragEvent dsde) { // no op } public void dropActionChanged(DragSourceDragEvent dsde) { // no op } private boolean isDropOnExplorer(Point location) { TreePath path = tree.getPathForLocation(tree.getWidth()-1, location.y); return (path == null); } private boolean isDropFilesOnDir(Point location, Transferable t) { File f = getFileAtLocation(location); if ((f == null) || !f.isDirectory()) return false; if (!FileDnd.testFileFlavor(t.getTransferDataFlavors())) return false; return true; } private boolean isDropNewFile(Point location, Transferable t) { File f = getFileAtLocation(location); if ((f == null) || !f.isDirectory()) return false; if (FileDnd.isPatchStringFlavor(t.getTransferDataFlavors())) return true; return false; } private TreeNode getNodeAtLocation(Point location) { TreePath path = tree.getPathForLocation(location.x, location.y); if (path == null) return null; Object c = path.getLastPathComponent(); if (c instanceof TreeNode) return (TreeNode)c; else return null; } private File getFileAtLocation(Point location) { TreeNode c = getNodeAtLocation(location); if (c instanceof FileNode) { // System.out.println("c " + c + " class " + c.getClass()); FileNode node = (FileNode)c; return node.getFile(); } else { return null; } } protected boolean updateDropAction(DropTargetDragEvent dtde) { if (isDropOnExplorer(dtde.getLocation())) dtde.acceptDrag(DnDConstants.ACTION_LINK); else if (isDropFilesOnDir(dtde.getLocation(), dtde.getTransferable())) dtde.acceptDrag(dtde.getDropAction() & (DnDConstants.ACTION_MOVE | DnDConstants.ACTION_COPY)); else if (isDropNewFile(dtde.getLocation(), dtde.getTransferable())) dtde.acceptDrag(DnDConstants.ACTION_COPY); else { dtde.rejectDrag(); return false; } return true; } protected void dumpTransferable(Transferable t) { /*System.out.println("dump"); for (DataFlavor f : t.getTransferDataFlavors()) { System.out.println("flavor " + f.getMimeType() + f.getPrimaryType() + " " + f.getSubType()); }*/ } public void dragEnter(DropTargetDragEvent dtde) { updateDropAction(dtde); } public void dragExit(DropTargetEvent dte) { tree.repaint(cueLine.getBounds()); timerHover.stop(); if (pathLastExpanded != null) { // tree.collapsePath(pathLastExpanded); pathLastExpanded = null; } pathLast = null; } public void dropActionChanged(DropTargetDragEvent dtde) { updateDropAction(dtde); } public void dragOver(DropTargetDragEvent dtde) { Point location = dtde.getLocation(); TreeUI treeUI = tree.getUI(); if (!(treeUI instanceof ExplorerTreeUI)) return; ExplorerTreeUI etUI = (ExplorerTreeUI) treeUI; etUI.updateScrollPosition(location); TreePath path = tree.getPathForLocation(location.x, location.y); if (!updateDropAction(dtde)) { tree.paintImmediately(cueLine.getBounds()); timerHover.stop(); return; } else { if (pathLastExpanded != null && pathLastExpanded != path && !pathLastExpanded.isDescendant(path)) { // tree.collapsePath(pathLastExpanded); // pathLastExpanded = null; } TreePath path2 = tree.getClosestPathForLocation(location.x, location.y); if (!(path2 == pathLast)) { pathLast = path2; timerHover.restart(); } if (path != null) { Rectangle raPath = tree.getPathBounds(path); if (raPath != null) { // Cue line bounds (2 pixels beneath the drop target) Rectangle rect = new Rectangle(0, raPath.y+(int)raPath.getHeight(), tree.getWidth(), 2); if (!cueLine.contains(rect)) { tree.paintImmediately(cueLine.getBounds()); } cueLine.setRect(rect); Graphics2D g2 = (Graphics2D) tree.getGraphics(); g2.setColor(colorCueLine); // The cue line color g2.fill(cueLine); // Draw the cue line } } } } public void drop(DropTargetDropEvent dtde) { tree.repaint(cueLine.getBounds()); timerHover.stop(); Point location = dtde.getLocation(); if (isDropOnExplorer(location)) { dtde.acceptDrop(DnDConstants.ACTION_LINK); createNewDirectoryLink(dtde); } else if (isDropFilesOnDir(location, dtde.getTransferable())) { // check copy, move if ((dtde.getDropAction() & DnDConstants.ACTION_MOVE) != 0) { dtde.acceptDrop(DnDConstants.ACTION_MOVE); dropFilesInDir(dtde, dtde.isLocalTransfer()); } else if ((dtde.getDropAction() & DnDConstants.ACTION_COPY) != 0) { dtde.acceptDrop(DnDConstants.ACTION_COPY); dropFilesInDir(dtde, false); } dtde.dropComplete(true); return; } else if (isDropNewFile(location, dtde.getTransferable())) { dtde.acceptDrop(DnDConstants.ACTION_COPY); dropNewFileInDir(dtde); dtde.dropComplete(true); return; } else { dtde.rejectDrop(); return; } } private void dropNewFileInDir(DropTargetDropEvent dtde) { Transferable t = dtde.getTransferable(); Point location = dtde.getLocation(); File dest = getFileAtLocation(location); TreeNode c = getNodeAtLocation(location); TreePath destPath = getClosestPathForLocation(tree, location.x, location.y); FileNode parNode = null; if (c instanceof FileNode) { parNode = (FileNode)c; } else { return; } if ((dest == null) || !dest.isDirectory()) return; // never too sure DataFlavor flavor = FileDnd.getPatchStringFlavor(t.getTransferDataFlavors()); if (flavor == null) return; try { String data = FileDnd.getPatchString(t); File newFile = FileUtils.newFileWithPrefix(dest, "macro", ".pch"); FileWriter out = new FileWriter(newFile); out.write(data); out.close(); parNode.updateChildrenNodes(true); ((ExplorerTree)tree).updateParentRootNodes(parNode); tree.expandPath(destPath); ((ExplorerTree)tree).fireNodeStructureChanged(parNode); for (TreeNode child : parNode.getChildren()) { if (child instanceof FileNode && ((FileNode)child).getFile().getCanonicalPath().equals(newFile.getCanonicalPath())) { tree.startEditingAtPath(new TreePath(((FileNode)child).getPath())); break; } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void dropFilesInDir(DropTargetDropEvent dtde, boolean move) { DataFlavor flavor = FileDnd.getFileFlavor(dtde.getCurrentDataFlavors()); if (flavor == null) return; Point location = dtde.getLocation(); File dest = getFileAtLocation(location); if ((dest == null) || !dest.isDirectory()) return; // never too sure TreePath destPath = getClosestPathForLocation(tree, location.x, location.y); TreeNode tdestNode = getNodeAtLocation(location); if (!(tdestNode instanceof FileNode)) return; FileNode destNode = (FileNode)tdestNode; Transferable t = dtde.getTransferable(); List<File> files = FileDnd.getTransferableFiles(flavor, t); FileFilter filter = destNode.getFileFilter(); if (files == null) return; for (File f : files) { if (f.exists()) { try { if (move) { boolean success = f.renameTo(new File(dest, f.getName())); } else { // recursively copy directory with only filtered files FileUtils.copy(f, new File(dest, f.getName()), filter); } } catch (Throwable e) { // XXX catch errors here // System.out.println("rename " + f + " to " + new File(dest, f.getName())); e.printStackTrace(); } } } try { if (t.isDataFlavorSupported(FileSelectionFlavor)) { Object o = t.getTransferData(FileSelectionFlavor); if (o instanceof FileSelectionTransferable) { for (FileNode node : ((FileSelectionTransferable)o).getSelectedFileNodes()) { TreeNode parent = node.getParent(); if (parent instanceof FileNode) { FileNode parNode = (FileNode)parent; File f1 = parNode.getFile(); if (parNode.updateChildrenNodes(true)) { ((ExplorerTree)tree).updateParentRootNodes(parNode); ((ExplorerTree)tree).fireNodeStructureChanged(parNode); } } } } } } catch (Throwable e) { e.printStackTrace(); } ((FileNode)destNode).updateChildrenNodes(true); tree.expandPath(destPath); ((ExplorerTree)tree).updateParentRootNodes(destNode); ((ExplorerTree)tree).fireNodeStructureChanged(destNode); } private void createNewDirectoryLink(DropTargetDropEvent dtde) { Transferable t = dtde.getTransferable(); try { if (t.isDataFlavorSupported(FileSelectionFlavor)) { Object o = t.getTransferData(FileSelectionFlavor); if (o instanceof FileSelectionTransferable) { Transferable ts[] = ((FileSelectionTransferable)o).getSelectedTransferables(); for (Transferable t2: ts) { Object o2 = t2.getTransferData(FileNode.fileNodeFlavor); FileNode node = (FileNode)o2; DataFlavor fileFlavor = FileDnd.getFileFlavor(t2.getTransferDataFlavors()); List<File> files = FileDnd.getTransferableFiles(fileFlavor, t2); for (File file: files) { if (file.isDirectory()) { addNewRootEntry(file, node == null ? null : node.getFileFilter()); } } } } } else { DataFlavor fileFlavor = FileDnd.getFileFlavor(t.getTransferDataFlavors()); List<File> files = FileDnd.getTransferableFiles(fileFlavor, t); for (File file: files) { if (file.isDirectory()) { addNewRootEntry(file, null); } } } } catch (Throwable e) { e.printStackTrace(); } } protected void addNewRootEntry(File file, FileFilter filter) { ExplorerTree eTree = (ExplorerTree)tree; FileContext fc = new FileContext(eTree, filter, file); eTree.addRootNode(fc); eTree.fireRootChanged(); } } }